#include <QApplication>
#include <QMetaObject>
#include <QJsonObject>
#include <QJsonArray>

#include <osg/Matrixd>
#include <osg/MatrixTransform>

#include <Geom2d_Curve.hxx>
#include <Geom2dAPI_InterCurveCurve.hxx>

#include "Ucs.h"
#include "OsgUtil.h"
#include "JsonReader.h"
#include "Registry.h"
#include "ActorTable.h"
#include "Scene.h"
#include "GLSLPrograms.h"

struct UcsPrivate
{
	UcsPrivate()
	{
	}
	std::set<std::shared_ptr<INode>> m_children;
	mutable std::vector<int> m_sortedIdTable; // ID从小到大排序
	mutable osg::ref_ptr<osg::MatrixTransform> m_rep3D;
	mutable osg::ref_ptr<osg::Geometry> m_mesh;
	// UCS 参数
	UCSKind m_kind;
	bool m_current = false;
	bool m_allowBuildXPoints = true;
	bool m_needBuildXPoints = false;
	mutable std::vector<osg::Vec2d> m_xpoints;
};

UCS::UCS(INode* parent)
	: Ent(parent)
	, d_ptr(new UcsPrivate)
{
	getRep3D();
}

void UCS::setKind(UCSKind kind)
{
	d_ptr->m_kind = kind;
	switch (kind)
	{
	case UCSKind::XOY:
		setAxis({ 1,0,0 }, { 0,1,0 });
		break;
	case UCSKind::YOZ:
		setAxis({ 0,1,0 }, { 0,0,1 });
		break;
	case UCSKind::ZOX:
		setAxis({ 1,0,0 }, { 0,0,1 });
		break;
	default:
		break;
	}
}

UCSKind UCS::getKind() const
{
	return d_ptr->m_kind;
}

bool UCS::setAxis(const osg::Vec3d& x, const osg::Vec3d& y)
{
	bool success = Plane::setAxis(x, y);
	if(success)
		addFlags(UPDATE_MATRIX);
	return success;
}

void UCS::save(QJsonObject & json) const
{
	Ent::save(json);
	//Plane::save(json);

	json["kind"] = (int)d_ptr->m_kind;

	QJsonArray childrenJson;
	for (auto id : d_ptr->m_sortedIdTable)
	{
		auto child = static_cast<Ent*>(getRoot()->getNodeByID(id));
		QJsonObject cjson;
		child->save(cjson);
		childrenJson.append(cjson);
	}
	json["children"] = childrenJson;
}

void UCS::load(const QJsonObject & json, JsonReader & reader)
{
	Ent::load(json, reader);
	//Plane::load(json);

	auto kind = (UCSKind)json["kind"].toInt();
	setKind(kind);

	auto childJson = json["children"].toArray();
	for (const auto& cjson : childJson)
	{
		auto cjsonobject = cjson.toObject();
		auto className = cjsonobject["class"].toString().toStdString();
		auto child = createInstance(className, this);
		if (child)
		{
			child->load(cjsonobject, reader);
			addChild(child);
		}
	}

	Scene* scene = static_cast<Scene*>(getRoot());
	scene->setUCS(getKind(), this);
}

osg::Node * UCS::getRep3D() const
{
	if (!d_ptr->m_rep3D)
	{
		d_ptr->m_rep3D = new osg::MatrixTransform();
		d_ptr->m_mesh = new osg::Geometry();
		d_ptr->m_mesh->setNodeMask(NoPickMask);
		d_ptr->m_rep3D->addChild(d_ptr->m_mesh);
		auto ss = d_ptr->m_rep3D->getOrCreateStateSet();
		osg::ref_ptr<osg::Program> program = createEdgeProgram();
		auto ss2 = d_ptr->m_mesh->getOrCreateStateSet();
		ss2->setAttributeAndModes(program, osg::StateAttribute::ON);
	}
	return d_ptr->m_rep3D.get();
}

void UCS::addChild(const std::shared_ptr<INode>& pChild)
{
	addChildImpl(pChild, d_ptr->m_children, d_ptr->m_sortedIdTable);
}

void UCS::removeChild(const std::shared_ptr<INode>& pChild)
{
	removeChildImpl(pChild, d_ptr->m_children, d_ptr->m_sortedIdTable);
}

void UCS::removeChildren()
{
	removeChildrenImpl(d_ptr->m_children, d_ptr->m_sortedIdTable);
}

const std::set<std::shared_ptr<INode>>& UCS::getChildren() const
{
	return d_ptr->m_children;
}

void UCS::update3D()
{
	if (flags() & UPDATE_MATRIX)
	{
		d_ptr->m_rep3D->setMatrix(getMatrix());
	}

	if (flags() & UPDATE_CHILDREN)
	{
		update3DChildrenImpl(d_ptr->m_children);
		if (d_ptr->m_allowBuildXPoints)
		{
			buildXPoints();
		}
		d_ptr->m_needBuildXPoints = !d_ptr->m_allowBuildXPoints;
	}
	removeFlags(0xFFFFFFFF);
}

void UCS::updateMesh(const osg::Vec4& color, double gridLen, int minXI, int maxXI, int minYI, int maxYI)
{
	d_ptr->m_mesh->getPrimitiveSetList().clear();
	if (maxXI > minXI && maxYI > minYI)
	{
		auto va = new osg::Vec3Array();
		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array(osg::Array::BIND_OVERALL);
		va->dirty();
		ca->dirty();
		d_ptr->m_mesh->setVertexArray(va);
		d_ptr->m_mesh->setColorArray(ca.get(), osg::Array::BIND_OVERALL);
		d_ptr->m_mesh->dirtyGLObjects();
		osg::Vec3d dx, dy;
		dx.set(gridLen, 0, 0);
		dy.set(0, gridLen, 0);
		// 画横线
		const double xGridCount = maxXI - minXI;
		for (int i = minYI; i <= maxYI; i++)
		{
			osg::Vec3d start = dx * minXI + dy * i;
			va->push_back(start);
			va->push_back(start + dx * xGridCount);
		}
		// 画竖线
		const double yGridCount = maxYI - minYI;
		for (int i = minXI; i <= maxXI; i++)
		{
			osg::Vec3d start = dy * minYI + dx * i;
			va->push_back(start);
			va->push_back(start + dy * yGridCount);
		}
		ca->push_back(color);
		int lineCount = (maxXI - minXI + 1) + (maxYI - minYI + 1);
		d_ptr->m_mesh->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 2 * lineCount));
	}
	else
	{
		d_ptr->m_mesh->setVertexArray(nullptr);
		d_ptr->m_mesh->setColorArray(nullptr, osg::Array::BIND_OVERALL);
		d_ptr->m_mesh->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 0));
	}
}

osg::Geometry * UCS::getMesh() const
{
	return d_ptr->m_mesh;
}

void UCS::visitChildren(INodeCallback lcb, bool preorder) const
{
	visitChildrenImpl(lcb, d_ptr->m_children, preorder);
}

bool UCS::isLeaf() const
{
	return false;
}

osg::Vec4 UCS::getUCSColor() const
{
	osg::Vec4 color;
	switch (d_ptr->m_kind)
	{
	case UCSKind::XOY:
		color.set(124 / 255.0, 252 / 255.0, 0 / 255.0, 1.0);
		break;
	case UCSKind::YOZ:
		color.set(238 / 255.0, 201 / 255.0, 0 / 255.0, 1.0);
		break;
	case UCSKind::ZOX:
		color.set(160 / 255.0, 32 / 255.0, 240 / 255.0, 1.0);
		break;
	default:
		break;
	}
	return color;
}

std::vector<gp_Pnt2d> intersect(Handle(Geom2d_Curve) curve1, Handle(Geom2d_Curve) curve2)
{
	std::vector<gp_Pnt2d> result;
	Geom2dAPI_InterCurveCurve icc;
	icc.Init(curve1, curve2);
	for (int i = 1; i <= icc.NbPoints(); ++i)
	{
		auto x = icc.Point(i);
		result.push_back(x);
	}
	return result;
}

void UCS::buildXPoints()
{
	std::vector<Handle(Geom2d_Curve)> curve2ds;
	const auto& children = getChildren();
	for (const auto& child : children)
	{
		auto curve2D = std::dynamic_pointer_cast<Ent2DCurve>(child);
		if (curve2D)
		{
			auto tmp = curve2D->getCurve2d();
			curve2ds.insert(curve2ds.end(), tmp.begin(), tmp.end());
		}
	}
	std::vector<osg::Vec2d> result;
	int curveCount = curve2ds.size();
	for (int i = 0; i < curveCount - 1; ++i)
	{
		for (int j = i + 1; j < curveCount; ++j)
		{
			std::vector<gp_Pnt2d> points = intersect(curve2ds[i], curve2ds[j]);
			for (const auto& pt : points)
			{
				result.push_back(osg::Vec2d(pt.X(), pt.Y()));
			}
		}
	}
	d_ptr->m_xpoints.swap(result);
}

std::vector<osg::Vec2d> UCS::getXPoints()
{
	return d_ptr->m_xpoints;
}

void UCS::pauseBuildXPoints()
{
	d_ptr->m_allowBuildXPoints = false;
}

void UCS::restoreBuildXPoints()
{
	d_ptr->m_allowBuildXPoints = true;
	if (d_ptr->m_needBuildXPoints)
		addFlags(UPDATE_CHILDREN); // UCS::update3D > buildXPoints will be called
}
